package com.sailing.xjpb.web.base.advice;

import com.sailing.xjpb.common.web.ActionResult;
import com.sailing.xjpb.common.web.ResponseCode;
import com.sailing.xjpb.common.web.ResultUtils;
import com.sailing.xjpb.web.base.advice.config.ResponseAdvisorConfigProperties;
import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 所有返回值包装为ActionResult
 *
 * @author YW
 * @see ActionResult
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
@SuppressWarnings({"unchecked"})
public class SailingResponseAdvisor implements ResponseBodyAdvice<Object> {

    /**
     * 默认swagger相关的请求不需要包装返回值
     */
    public static final String DEFAULT_NO_WRAP_URL = "/**/swagger*/**,/**/api-docs,/**/health/status";
    private final PathMatcher matcher = new AntPathMatcher();
    private final List<String> noWrapUrls = new ArrayList<>();
    @Autowired
    ResponseAdvisorConfigProperties config;

    @Override
    public boolean supports(@NonNull MethodParameter returnType,
                            @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType,
                                  @NonNull MediaType selectedContentType,
                                  @NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  @NonNull ServerHttpRequest request,
                                  @NonNull ServerHttpResponse response) {

        if (!MediaType.APPLICATION_JSON.isCompatibleWith(selectedContentType)) {
            return body;
        }
        String requestPath = request.getURI().getPath();
        // 处理404情况
        ServletServerHttpResponse httpResponse = (ServletServerHttpResponse) response;
        if (httpResponse.getServletResponse().getStatus() == HttpStatus.NOT_FOUND.value()) {
            return ResultUtils.error(ResponseCode.CUSTOM_ERROR, "请求地址未找到（404）！");
        }
        // 处理不包装返回值的请求
        if (isNoWrapResponseUrl(requestPath)) {
            return rewritePage(body);
        }
        // 统一包装为ActionResult
        ActionResult<Object> actionResult;
        if (body instanceof ActionResult) {
            actionResult = (ActionResult<Object>) body;
        } else {
            actionResult = ResultUtils.success(body);
        }
        actionResult.setPath(requestPath);
        Object result = actionResult.getResult();
        actionResult.setResult(this.rewritePage(result));
        return actionResult;
    }

    private boolean isNoWrapResponseUrl(String requestUrl) {
        this.initNoWrapUrls();
        for (String url : noWrapUrls) {
            if (matcher.match(url, requestUrl)) {
                return true;
            }
        }
        return false;
    }

    private void initNoWrapUrls() {
        if (noWrapUrls.isEmpty()) {
            synchronized (this) {
                if (noWrapUrls.isEmpty()) {
                    noWrapUrls.addAll(Arrays.asList(DEFAULT_NO_WRAP_URL.split(",")));
                    String configNoWrapUrl = config.getNoWrapUrl();
                    if (configNoWrapUrl != null) {
                        noWrapUrls.addAll(Arrays.asList(configNoWrapUrl.split(",")));
                    }
                }
            }
        }
    }

    private Object rewritePage(Object result) {
        if (config.isRewritePage() && result instanceof Page && Pageable.unpaged() != result) {
            Page<?> page = ((Page<?>) result);
            List<?> content = page.getContent();
            long total = page.getTotalElements();
            Pageable pageable = SailingPageRequest.of(page.getPageable());
            return new SailingPageImpl<>(content, pageable, total);
        }
        return result;
    }

}
